﻿using System;
using System.Collections.Generic;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Moq;
using Xpedite.Resources;
using Xpedite.Resources.Definitions;
using Xpedite.Localization;
using Xpedite.WebAbstraction;

/* Copyright (c) 2010 CBaxter
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), 
 * to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, 
 * and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 
 * IN THE SOFTWARE. 
 */

namespace Xpedite.Test.Resources
{
  [TestClass]
  public class ResourceUrlGeneratorTest
  {
    private const String WebSiteUrlBase = "http://localhost";
    private const String ApplicationPath = "/VirtualDirectory";
    private const String VirtualDirectoryUrlBase = WebSiteUrlBase + ApplicationPath;

    private readonly Func<IWebRequest> _requestRetriever;
    private readonly Mock<IWebRequest> _webRequest = new Mock<IWebRequest>(MockBehavior.Strict);

    public ResourceUrlGeneratorTest()
    {
      _webRequest = CreateWebRequest(VirtualDirectoryUrlBase);
      _requestRetriever = () => _webRequest.Object;
    }

    [TestMethod]
    public void TestGenerateUrlForPath()
    {
      var generator = new ResourceUrlGenerator(_requestRetriever, ApplicationPath);

      _webRequest.Setup(mock => mock.MapPath(ApplicationPath + "/xpedite.sprite.xcr")).Returns((String)null);

      Assert.AreEqual(ApplicationPath + "/xpedite.sprite.xcr", generator.GenerateUrlFor(ApplicationPath + "/xpedite.sprite.xcr").VirtualPath);
    }

    [TestMethod]
    public void TestGenerateUrlForResourceUrl()
    {
      var generator = new ResourceUrlGenerator(_requestRetriever, ApplicationPath);
      var resourceReference = CreateLocalResourceReference(new Uri(VirtualDirectoryUrlBase + "/default.js", UriKind.Absolute));

      _webRequest.Setup(mock => mock.MapPath(ApplicationPath + "/xpedite.593D6930.default.js.xcr")).Returns((String)null);

      Assert.AreEqual(ApplicationPath + "/xpedite.593D6930.default.js.xcr", generator.GenerateUrlFor(resourceReference.Object).VirtualPath);
    }

    [TestMethod]
    public void TestGenerateUrlForResourceUrlIgnoresHostIfLocal()
    {
      var webRequest1 = CreateWebRequest("http://host1");
      var generator1 = new ResourceUrlGenerator(() => webRequest1.Object, ApplicationPath);
      var resourceReference1 = CreateLocalResourceReference(new Uri("http://host1" + ApplicationPath + "/default.js", UriKind.Absolute));

      var webRequest2 = CreateWebRequest("http://host2");
      var generator2 = new ResourceUrlGenerator(() => webRequest2.Object, ApplicationPath);
      var resourceReference2 = CreateLocalResourceReference(new Uri("http://host2" + ApplicationPath + "/default.js", UriKind.Absolute));

      webRequest1.Setup(mock => mock.MapPath(ApplicationPath + "/xpedite.593D6930.default.js.xcr")).Returns((String)null);
      webRequest2.Setup(mock => mock.MapPath(ApplicationPath + "/xpedite.593D6930.default.js.xcr")).Returns((String)null);

      Assert.AreEqual(ApplicationPath + "/xpedite.593D6930.default.js.xcr", generator1.GenerateUrlFor(resourceReference1.Object).VirtualPath);
      Assert.AreEqual(ApplicationPath + "/xpedite.593D6930.default.js.xcr", generator2.GenerateUrlFor(resourceReference2.Object).VirtualPath);
    }

    [TestMethod]
    public void TestGenerateUrlForResourceUrlDiffersIfRemote()
    {
      var generator1 = new ResourceUrlGenerator(_requestRetriever, ApplicationPath);
      var resourceReference1 = CreateLocalResourceReference(new Uri("http://host1" + ApplicationPath + "/default.js", UriKind.Absolute));

      var generator2 = new ResourceUrlGenerator(_requestRetriever, ApplicationPath);
      var resourceReference2 = CreateRemoteResourceReference(new Uri("http://host2" + ApplicationPath + "/default.js", UriKind.Absolute));

      _webRequest.Setup(mock => mock.MapPath(ApplicationPath + "/xpedite.593D6930.default.js.xcr")).Returns((String)null);
      _webRequest.Setup(mock => mock.MapPath(ApplicationPath + "/xpedite.F62F1B9E.default.js.xcr")).Returns((String)null);

      Assert.AreEqual(ApplicationPath + "/xpedite.593D6930.default.js.xcr", generator1.GenerateUrlFor(resourceReference1.Object).VirtualPath);
      Assert.AreEqual(ApplicationPath + "/xpedite.F62F1B9E.default.js.xcr", generator2.GenerateUrlFor(resourceReference2.Object).VirtualPath);
    }

    [TestMethod]
    public void TestGenerateUrlForResourceDefinitionWithNoResourceReferences()
    {
      var generator = new ResourceUrlGenerator(_requestRetriever, ApplicationPath);
      var definition = CreateResourceDefinition(ResourceType.Css, "Name1", new IResourceContainer[0]);

      _webRequest.Setup(mock => mock.MapPath(ApplicationPath + "/xpedite.71484B800000031A.Name1.css.xcr")).Returns((String)null);

      Assert.AreEqual(ApplicationPath + "/xpedite.71484B800000031A.Name1.css.xcr", generator.GenerateUrlFor(definition.Object).VirtualPath);
    }

    [TestMethod]
    public void TestGenerateUrlForResourceDefinitionWithSingleResourceReference()
    {
      var generator = new ResourceUrlGenerator(_requestRetriever, ApplicationPath);
      var definition = CreateResourceDefinition(ResourceType.Css, "Name1", new[] 
      { 
        CreateResourceContainer(CreateLocalResourceReference( new Uri(WebSiteUrlBase + "/default.js", UriKind.Absolute)).Object, new DateTime(2010, 09, 01)).Object 
      });

      _webRequest.Setup(mock => mock.MapPath(ApplicationPath + "/xpedite.3E7C65FCC98F982D.Name1.css.xcr")).Returns((String)null);

      Assert.AreEqual(ApplicationPath + "/xpedite.3E7C65FCC98F982D.Name1.css.xcr", generator.GenerateUrlFor(definition.Object).VirtualPath);
    }

    [TestMethod]
    public void TestGenerateUrlForResourceDefinitionWithMultipleResourceReferences()
    {
      var generator = new ResourceUrlGenerator(_requestRetriever, ApplicationPath);
      var definition = CreateResourceDefinition(ResourceType.Css, "Name1", new[] 
      { 
        CreateResourceContainer(CreateLocalResourceReference( new Uri(WebSiteUrlBase + "/core.js", UriKind.Absolute)).Object, new DateTime(2010, 09, 03)).Object,
        CreateResourceContainer(CreateLocalResourceReference(  new Uri(WebSiteUrlBase + "/default.js", UriKind.Absolute)).Object, new DateTime(2010, 09, 01)).Object 
      });

      _webRequest.Setup(mock => mock.MapPath(ApplicationPath + "/xpedite.5DEAC7CF0139D63F.Name1.css.xcr")).Returns((String)null);

      Assert.AreEqual(ApplicationPath + "/xpedite.5DEAC7CF0139D63F.Name1.css.xcr", generator.GenerateUrlFor(definition.Object).VirtualPath);
    }

    [TestMethod]
    public void TestGenerateUrlForResourceDefinitionHashDiffersOnResourceOrder()
    {
      var generator = new ResourceUrlGenerator(_requestRetriever, ApplicationPath);
      var definition1 = CreateResourceDefinition(ResourceType.Css, "Name1", new[] 
      { 
        CreateResourceContainer(CreateLocalResourceReference(  new Uri(WebSiteUrlBase + "/core.js", UriKind.Absolute)).Object, new DateTime(2010, 09, 03)).Object,
        CreateResourceContainer(CreateLocalResourceReference(  new Uri(WebSiteUrlBase + "/default.js", UriKind.Absolute)).Object, new DateTime(2010, 09, 01)).Object 
      });
      var definition2 = CreateResourceDefinition(ResourceType.Css, "Name1", new[] 
      { 
        CreateResourceContainer(CreateLocalResourceReference(   new Uri(WebSiteUrlBase + "/default.js", UriKind.Absolute)).Object, new DateTime(2010, 09, 01)).Object,
        CreateResourceContainer(CreateLocalResourceReference(  new Uri(WebSiteUrlBase + "/core.js", UriKind.Absolute)).Object, new DateTime(2010, 09, 03)).Object
      });

      _webRequest.Setup(mock => mock.MapPath(ApplicationPath + "/xpedite.5DEAC7CF0139D63F.Name1.css.xcr")).Returns((String)null);
      _webRequest.Setup(mock => mock.MapPath(ApplicationPath + "/xpedite.37A31C738D7A24DB.Name1.css.xcr")).Returns((String)null);

      Assert.AreNotEqual(generator.GenerateUrlFor(definition1.Object), generator.GenerateUrlFor(definition2.Object));
      Assert.AreEqual(ApplicationPath + "/xpedite.5DEAC7CF0139D63F.Name1.css.xcr", generator.GenerateUrlFor(definition1.Object).VirtualPath);
      Assert.AreEqual(ApplicationPath + "/xpedite.37A31C738D7A24DB.Name1.css.xcr", generator.GenerateUrlFor(definition2.Object).VirtualPath);
    }

    [TestMethod]
    public void TestGenerateUrlForResourceDefinitionHashDiffersOnTimeStampChange()
    {
      var generator = new ResourceUrlGenerator(_requestRetriever, ApplicationPath);
      var definition1 = CreateResourceDefinition(ResourceType.Css, "Name1", new[] 
      { 
        CreateResourceContainer(CreateLocalResourceReference( new Uri(WebSiteUrlBase + "/default.js", UriKind.Absolute)).Object, new DateTime(2010, 09, 01, 12, 13, 01, 500)).Object 
      });
      var definition2 = CreateResourceDefinition(ResourceType.Css, "Name1", new[] 
      { 
        CreateResourceContainer(CreateLocalResourceReference( new Uri(WebSiteUrlBase + "/default.js", UriKind.Absolute)).Object, new DateTime(2010, 09, 01, 12, 13, 01, 501)).Object
      });

      _webRequest.Setup(mock => mock.MapPath(ApplicationPath + "/xpedite.3E7C65FC20835774.Name1.css.xcr")).Returns((String)null);
      _webRequest.Setup(mock => mock.MapPath(ApplicationPath + "/xpedite.3E7C65FC20836E64.Name1.css.xcr")).Returns((String)null);

      Assert.AreNotEqual(generator.GenerateUrlFor(definition1.Object), generator.GenerateUrlFor(definition2.Object));
      Assert.AreEqual(ApplicationPath + "/xpedite.3E7C65FC20835774.Name1.css.xcr", generator.GenerateUrlFor(definition1.Object).VirtualPath);
      Assert.AreEqual(ApplicationPath + "/xpedite.3E7C65FC20836E64.Name1.css.xcr", generator.GenerateUrlFor(definition2.Object).VirtualPath);
    }

    [TestMethod]
    public void TestGenerateResourceDefinitionHashDiffersOnResourceType()
    {
      var generator = new ResourceUrlGenerator(_requestRetriever, ApplicationPath);
      var definition1 = CreateResourceDefinition(ResourceType.Css, "Name1", new[] 
      { 
        CreateResourceContainer(CreateLocalResourceReference(  new Uri(WebSiteUrlBase + "/default.js", UriKind.Absolute)).Object, new DateTime(2010, 09, 01)).Object 
      });
      var definition2 = CreateResourceDefinition(ResourceType.Gif, "Name1", new[] 
      { 
        CreateResourceContainer(CreateLocalResourceReference(  new Uri(WebSiteUrlBase + "/default.js", UriKind.Absolute)).Object, new DateTime(2010, 09, 01)).Object
      });
      var definition3 = CreateResourceDefinition(ResourceType.JavaScript, "Name1", new[] 
      { 
        CreateResourceContainer(CreateLocalResourceReference(  new Uri(WebSiteUrlBase + "/default.js", UriKind.Absolute)).Object, new DateTime(2010, 09, 01)).Object
      });
      var definition4 = CreateResourceDefinition(ResourceType.Jpeg, "Name1", new[] 
      { 
        CreateResourceContainer(CreateLocalResourceReference( new Uri(WebSiteUrlBase + "/default.js", UriKind.Absolute)).Object, new DateTime(2010, 09, 01)).Object
      });
      var definition5 = CreateResourceDefinition(ResourceType.Png, "Name1", new[] 
      { 
        CreateResourceContainer(CreateLocalResourceReference(   new Uri(WebSiteUrlBase + "/default.js", UriKind.Absolute)).Object, new DateTime(2010, 09, 01)).Object
      });
      var definition6 = CreateResourceDefinition(ResourceType.Unknown, "Name1", new[] 
      { 
        CreateResourceContainer(CreateLocalResourceReference( new Uri(WebSiteUrlBase + "/default.js", UriKind.Absolute)).Object, new DateTime(2010, 09, 01)).Object
      });

      _webRequest.Setup(mock => mock.MapPath(ApplicationPath + "/xpedite.3E7C65FCC98F982D.Name1.css.xcr")).Returns((String)null);
      _webRequest.Setup(mock => mock.MapPath(ApplicationPath + "/xpedite.3E7C65FCC9853A89.Name1.gif.xcr")).Returns((String)null);
      _webRequest.Setup(mock => mock.MapPath(ApplicationPath + "/xpedite.3E7C65FCC98C6184.Name1.js.xcr")).Returns((String)null);
      _webRequest.Setup(mock => mock.MapPath(ApplicationPath + "/xpedite.3E7C65FCC982C9DB.Name1.jpg.xcr")).Returns((String)null);
      _webRequest.Setup(mock => mock.MapPath(ApplicationPath + "/xpedite.3E7C65FCC9875132.Name1.png.xcr")).Returns((String)null);
      _webRequest.Setup(mock => mock.MapPath(ApplicationPath + "/xpedite.3E7C65FCC98930D6.Name1.Unknown.xcr")).Returns((String)null);

      Assert.AreEqual(ApplicationPath + "/xpedite.3E7C65FCC98F982D.Name1.css.xcr", generator.GenerateUrlFor(definition1.Object).VirtualPath);
      Assert.AreEqual(ApplicationPath + "/xpedite.3E7C65FCC9853A89.Name1.gif.xcr", generator.GenerateUrlFor(definition2.Object).VirtualPath);
      Assert.AreEqual(ApplicationPath + "/xpedite.3E7C65FCC98C6184.Name1.js.xcr", generator.GenerateUrlFor(definition3.Object).VirtualPath);
      Assert.AreEqual(ApplicationPath + "/xpedite.3E7C65FCC982C9DB.Name1.jpg.xcr", generator.GenerateUrlFor(definition4.Object).VirtualPath);
      Assert.AreEqual(ApplicationPath + "/xpedite.3E7C65FCC9875132.Name1.png.xcr", generator.GenerateUrlFor(definition5.Object).VirtualPath);
      Assert.AreEqual(ApplicationPath + "/xpedite.3E7C65FCC98930D6.Name1.Unknown.xcr", generator.GenerateUrlFor(definition6.Object).VirtualPath);
    }

    [TestMethod]
    public void TestGenerateResourceDefinitionHashDiffersOnResourceName()
    {
      var generator = new ResourceUrlGenerator(_requestRetriever, ApplicationPath);
      var definition1 = CreateResourceDefinition(ResourceType.Css, "Name1", new[] 
      { 
        CreateResourceContainer(CreateLocalResourceReference(new Uri(WebSiteUrlBase + "/default.js", UriKind.Absolute)).Object, new DateTime(2010, 09, 01)).Object 
      });
      var definition2 = CreateResourceDefinition(ResourceType.Css, "Name2", new[] 
      { 
        CreateResourceContainer(CreateLocalResourceReference(new Uri(WebSiteUrlBase + "/default.js", UriKind.Absolute)).Object, new DateTime(2010, 09, 01)).Object
      });

      _webRequest.Setup(mock => mock.MapPath(ApplicationPath + "/xpedite.3E7C65FCC98F982D.Name1.css.xcr")).Returns((String)null);
      _webRequest.Setup(mock => mock.MapPath(ApplicationPath + "/xpedite.3E7BDDABC98F982D.Name2.css.xcr")).Returns((String)null);

      Assert.AreEqual(ApplicationPath + "/xpedite.3E7C65FCC98F982D.Name1.css.xcr", generator.GenerateUrlFor(definition1.Object).VirtualPath);
      Assert.AreEqual(ApplicationPath + "/xpedite.3E7BDDABC98F982D.Name2.css.xcr", generator.GenerateUrlFor(definition2.Object).VirtualPath);
    }

    private Mock<IResourceUrl> CreateLocalResourceReference(Uri url)
    {
      var resourceUrl = new Mock<IResourceUrl>(MockBehavior.Strict);

      resourceUrl.SetupGet(mock => mock.VirtualPath).Returns(url.AbsolutePath);
      resourceUrl.SetupGet(mock => mock.IsLocal).Returns(true);
      resourceUrl.SetupGet(mock => mock.PhysicalPath).Returns(@"C:\inetpub\wwwroot\" + url.AbsolutePath.Replace('/', '\\'));

      return resourceUrl;
    }

    private Mock<IResourceUrl> CreateRemoteResourceReference(Uri url)
    {
      var resourceUrl = new Mock<IResourceUrl>(MockBehavior.Strict);

      resourceUrl.SetupGet(mock => mock.VirtualPath).Returns(url.AbsoluteUri);
      resourceUrl.SetupGet(mock => mock.IsLocal).Returns(false);
      resourceUrl.SetupGet(mock => mock.PhysicalPath).Returns((String)null);

      return resourceUrl;
    }

    private Mock<IResourceContainer> CreateResourceContainer(IResourceUrl resourceUrl, DateTime lastWriteTime)
    {
      var resourceContainer = new Mock<IResourceContainer>(MockBehavior.Strict);
      var resource = new Mock<IResource>(MockBehavior.Strict);

      resource.SetupGet(mock => mock.LastModified).Returns(lastWriteTime);

      resourceContainer.SetupGet(mock => mock.Resource).Returns(resource.Object);
      resourceContainer.SetupGet(mock => mock.ResourceUrl).Returns(resourceUrl);

      return resourceContainer;
    }

    private Mock<IResourceDefinition> CreateResourceDefinition(ResourceType type, String name, IEnumerable<IResourceContainer> resources)
    {
      var resourceDefinition = new Mock<IResourceDefinition>(MockBehavior.Strict);

      resourceDefinition.SetupGet(mock => mock.Resources).Returns(resources);
      resourceDefinition.SetupGet(mock => mock.ReferenceName).Returns(name);
      resourceDefinition.SetupGet(mock => mock.ResourceType).Returns(type);

      return resourceDefinition;
    }

    private static Mock<IWebRequest> CreateWebRequest(String webSiteUrlBase)
    {
      var webRequest = new Mock<IWebRequest>(MockBehavior.Strict);

      webRequest.SetupGet(mock => mock.ApplicationPath).Returns(ApplicationPath);
      webRequest.SetupGet(mock => mock.Url).Returns(new Uri(webSiteUrlBase + ApplicationPath + "/Default.aspx", UriKind.Absolute));

      return webRequest;
    }
  }
}
